<!doctype html>
<html lang="zh-cn">
  <head>
    <title>闭包和装饰器 // JW Blog</title>
    <meta charset="utf-8" />
    <meta name="generator" content="Hugo 0.60.1" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="author" content="John Doe" />
    <meta name="description" content="" />
    <link rel="stylesheet" href="https://duyi111.gitee.io/css/main.min.f90f5edd436ec7b74ad05479a05705770306911f721193e7845948fb07fe1335.css" />

    
    <meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="闭包和装饰器"/>
<meta name="twitter:description" content="1.闭包 1.1 闭包的介绍  我们知道当函数调用完，函数内定义的变量都销毁了，但是我们有时候需要保存函数内的这个变量，每次在这个变量的基础上完成一些列的操作，比如: 每次在这个变量的基础上和其它数字进行求和计算，那怎么办呢?我们就可以通过闭包来解决这个需求 闭包的定义：  在函数嵌套的前提下，内部函数使用了外部函数的变量，并且外部函数返回了内部函数，我们把这个使用外部函数变量的内部函数称为闭包    1.2 闭包的构成条件  1.在函数嵌套（函数里面再定义函数）的前提下 2.内部函数使用了外部函数的变量（还包括外部函数的参数） 3.外部函数返回了内部函数  1.3 简单闭包的示例代码 # 定义一个外部函数 def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 内部函数使用了外部函数的变量（num1） result = num1 &#43; num2 print(&#39;结果是:&#39;, result) # 外部函数返回了内部函数，这里返回的内部函数就是闭包 return func_inner # 创建闭包实例 f = func_out(1) # 执行闭包 f(2) f(3) # 运行结果： 结果是：3 结果是：4  通过上面的输出结果可以看出闭包保存了外部函数内的变量num1，每次执行闭包都是在num1=1基础上进行计算  1.4 闭包的作用  闭包可以保存外部函数内的变量，不会随着外部函数调用完而销毁。 由于闭包引用了外部函数的变量，则外部函数的变量没有及时释放，消耗内存。 可以提高代码的可重用性。  1.5 小结  1.当返回的内部函数使用了外部函数的变量就形成了闭包。 2.闭包可以对外部函数的变量进行保存。 实现闭包的标准格式 # 外部函数 def func_out(a): b = 10 # 内部函数 def func_inner(): # 内部函数使用了外部函数的变量或者参数 print(a, b) # 返回内部函数，这里返回的内部函数就是闭包实例 return func_inner   2."/>

    <meta property="og:title" content="闭包和装饰器" />
<meta property="og:description" content="1.闭包 1.1 闭包的介绍  我们知道当函数调用完，函数内定义的变量都销毁了，但是我们有时候需要保存函数内的这个变量，每次在这个变量的基础上完成一些列的操作，比如: 每次在这个变量的基础上和其它数字进行求和计算，那怎么办呢?我们就可以通过闭包来解决这个需求 闭包的定义：  在函数嵌套的前提下，内部函数使用了外部函数的变量，并且外部函数返回了内部函数，我们把这个使用外部函数变量的内部函数称为闭包    1.2 闭包的构成条件  1.在函数嵌套（函数里面再定义函数）的前提下 2.内部函数使用了外部函数的变量（还包括外部函数的参数） 3.外部函数返回了内部函数  1.3 简单闭包的示例代码 # 定义一个外部函数 def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 内部函数使用了外部函数的变量（num1） result = num1 &#43; num2 print(&#39;结果是:&#39;, result) # 外部函数返回了内部函数，这里返回的内部函数就是闭包 return func_inner # 创建闭包实例 f = func_out(1) # 执行闭包 f(2) f(3) # 运行结果： 结果是：3 结果是：4  通过上面的输出结果可以看出闭包保存了外部函数内的变量num1，每次执行闭包都是在num1=1基础上进行计算  1.4 闭包的作用  闭包可以保存外部函数内的变量，不会随着外部函数调用完而销毁。 由于闭包引用了外部函数的变量，则外部函数的变量没有及时释放，消耗内存。 可以提高代码的可重用性。  1.5 小结  1.当返回的内部函数使用了外部函数的变量就形成了闭包。 2.闭包可以对外部函数的变量进行保存。 实现闭包的标准格式 # 外部函数 def func_out(a): b = 10 # 内部函数 def func_inner(): # 内部函数使用了外部函数的变量或者参数 print(a, b) # 返回内部函数，这里返回的内部函数就是闭包实例 return func_inner   2." />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://duyi111.gitee.io/post/%E9%97%AD%E5%8C%85%E5%92%8C%E8%A3%85%E9%A5%B0%E5%99%A8/" />
<meta property="article:published_time" content="2020-02-05T17:50:14+08:00" />
<meta property="article:modified_time" content="2020-02-05T17:50:14+08:00" />


  </head>
  <body>
    <header class="app-header">
      <a href="https://duyi111.gitee.io/"><img class="app-header-avatar" src="/avatar.jpg" alt="John Doe" /></a>
      <h1>JW Blog</h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vehicula turpis sit amet elit pretium.</p>
      <div class="app-header-social">
        
      </div>
    </header>
    <main class="app-container">
      
  <article class="post">
    <header class="post-header">
      <h1 class ="post-title">闭包和装饰器</h1>
      <div class="post-meta">
        <div>
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-calendar">
  <title>calendar</title>
  <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
          Feb 5, 2020
        </div>
        <div>
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-clock">
  <title>clock</title>
  <circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>
</svg>
          3 min read
        </div></div>
    </header>
    <div class="post-content">
      <h2 id="1">1.闭包</h2>
<h4 id="11-">1.1 闭包的介绍</h4>
<ul>
<li>我们知道当函数调用完，函数内定义的变量都销毁了，但是我们有时候需要保存函数内的这个变量，每次在这个变量的基础上完成一些列的操作，比如: 每次在这个变量的基础上和其它数字进行求和计算，那怎么办呢?我们就可以通过<em><strong>闭包</strong></em>来解决这个需求</li>
<li>闭包的定义：
<ul>
<li>在函数嵌套的前提下，内部函数使用了外部函数的变量，并且外部函数返回了内部函数，我们把这个<em><strong>使用外部函数变量的内部函数称为闭包</strong></em></li>
</ul>
</li>
</ul>
<h4 id="12-">1.2 闭包的构成条件</h4>
<ul>
<li>1.在函数嵌套（函数里面再定义函数）的前提下</li>
<li>2.内部函数使用了外部函数的变量（还包括外部函数的参数）</li>
<li>3.外部函数返回了内部函数</li>
</ul>
<h4 id="13-">1.3 简单闭包的示例代码</h4>
<pre><code># 定义一个外部函数
def func_out(num1):
    # 定义一个内部函数
    def func_inner(num2):
        # 内部函数使用了外部函数的变量（num1）
        result = num1 + num2
        print('结果是:', result)
    # 外部函数返回了内部函数，这里返回的内部函数就是闭包
    return func_inner

# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)
f(3)


# 运行结果：
结果是：3
结果是：4

</code></pre><ul>
<li>通过上面的输出结果可以看出闭包保存了外部函数内的变量num1，每次执行闭包都是在num1=1基础上进行计算</li>
</ul>
<h4 id="14-">1.4 闭包的作用</h4>
<ul>
<li>闭包可以保存外部函数内的变量，不会随着外部函数调用完而销毁。</li>
<li><em><strong>由于闭包引用了外部函数的变量，则外部函数的变量没有及时释放，消耗内存。</strong></em></li>
<li>可以提高代码的可重用性。</li>
</ul>
<h4 id="15-">1.5 小结</h4>
<ul>
<li>1.当返回的内部函数使用了外部函数的变量就形成了闭包。</li>
<li>2.闭包可以对外部函数的变量进行保存。</li>
<li>实现闭包的标准格式
<pre><code># 外部函数
def func_out(a):
    b = 10
    # 内部函数
    def func_inner():
        # 内部函数使用了外部函数的变量或者参数
        print(a, b)
    # 返回内部函数，这里返回的内部函数就是闭包实例
    return func_inner
</code></pre></li>
</ul>
<h2 id="2">2.修改闭包内使用的外部变量</h2>
<h4 id="21-">2.1 修改闭包内使用的外部变量的代码</h4>
<pre><code># 定义一个外部函数
def func_out(num1):
    # 定义一个内部函数
    def func_inner(num2):
        # 这里本意想要修改外部num1的值，实际上是在内部函数定义了一个局部变量num1
        nonlocal num1
        # 修改外部变量num1
        num1 = 10
        # 内部函数使用了外部函数的变量num1
        result = num1 + num2
        print('结果是：', result)
    
    print(num1)
    func_inner(1)
    print(num1)
    
    # 外部函数返回了内部函数，这里返回的内部函数就是闭包
    return func_inner
# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)
</code></pre><h4 id="22-">2.2 小结</h4>
<ul>
<li>修改闭包内使用的外部函数变量使用nonlocal关键字来完成</li>
</ul>
<h2 id="3">3.装饰器</h2>
<h4 id="31-">3.1 装饰器的定义</h4>
<ul>
<li>就是<em><strong>给已有函数增加额外功能的函数，它本质上就是一个闭包函数。</strong></em></li>
<li>装饰器的功能特点:
<ul>
<li>1.不修改已有函数的源代码</li>
<li>2.不修改已有函数的调用方式</li>
<li>3.给已有函数增加额外的功能</li>
</ul>
</li>
</ul>
<h4 id="32-">3.2 装饰器的示例代码</h4>
<pre><code># 添加一个登录验证的功能
def check(fn):
    def inner():
        print('请先登录。。。')
        fn()
    return inner

def comment():
    print('发表评论')
    
# 使用装饰器来装饰函数
comment = check(comment)
comment()


# 装饰器的基本雏形
def decoration(fn):  # fn是目标函数
    def inner():
        '''执行函数之前'''
        fn()  # 执行被装饰的函数
        '''执行函数之后'''
    return inner    
</code></pre><ul>
<li>闭包函数有且只有一个参数，必须是函数类型，这样定义的函数才是装饰器。</li>
<li>写代码要遵循开放封闭原则，它规定已经实现的功能代码不允许被修改，但可以被扩展</li>
</ul>
<h4 id="33-">3.3 装饰器的语法&ndash;糖写法</h4>
<ul>
<li>如果有多个函数都需要添加登录验证的功能，每次都需要编写func = check(func)这样代码对已有函数进行装饰，这样做法还是比较麻烦。</li>
<li>Python给提供了一个装饰函数更加简单的写法，那就是语法糖，语法糖的书写格式是：<code>@装饰器名字</code>，通过语法糖的方式也可以完成对已有函数的装饰
<pre><code># 添加一个登录验证的功能
def check(fn):
    print('装饰器函数执行了')
    def inner():
        print('请先登录。。。')
        fn()
    return inner
  
# 使用语法糖方式来装饰函数
@check
def comment():
    print('发表评论')
      
  
comment()    
</code></pre><ul>
<li><code>@check</code>等价于<code>comment = check(comment)</code></li>
<li>装饰器的执行时间是加载模块时立即执行</li>
</ul>
</li>
</ul>
<h4 id="34-">3.4 小结</h4>
<ul>
<li>装饰器本质上就是一个闭包函数，它可以对已有函数进行额外的功能扩展。</li>
<li>装饰器的语法格式：
<pre><code># 装饰器
def decoration(fn):  # fn:被装饰的目标函数
    def inner():
        '''执行函数之前'''
        fn() # 执行被装饰的目标函数
        '''执行函数之后'''
    return inner    
</code></pre></li>
<li>装饰器的语法<em><strong>糖写法：</strong></em>@装饰器名称，同样可以完成水已有函数的装饰操作。</li>
</ul>
<h2 id="4">4.通用装饰器的使用</h2>
<h4 id="41-">4.1 装饰带有参数的函数</h4>
<pre><code># 添加输出日志的功能
def logging(fn):
    def inner(num1, num2):
        print('--正在努力计算--')
        fn(num1, num2)
    
    return inner

# 使用装饰器装饰函数
@logging
def sum_num(a, b)
    result = a + b
    print(result)
    
sum_num(1, 2)


# 运行结果：
--正在努力计算--
3
</code></pre><h4 id="42-">4.2 装饰带有返回值的函数</h4>
<pre><code># 添加输出日志的功能
def logging(fn):
    def inner(num1, num2):
        print('--正在努力计算--')
        result = fn(num1, num2)
        return result 
    
    return inner
    
# 使用装饰器装饰函数
@logging
def sum_num(a, b):
    result = a + b
    return result

result = sum_num(1, 2)
print(result)


# 运行结果：
--正在努力计算--
3
</code></pre><h4 id="43-">4.3 装饰带有不定长参数的函数</h4>
<pre><code># 添加输出日志的功能
def logging(fn):
    def inner(*args, **kwargs):
        print('--正在努力计算--')
        fn(*args, **kwargs)
    return inner    

# 使用语法糖装饰函数
@logging
def sun_num(*args, **kwargs):
    result = 0
    for value in args:
        result += value
    
    for value in kwargs.values():
        result += value
        
    print(result)
    

sum_num(1, 2, a = 10) 


# 运行结果：
--正在努力计算--
13
</code></pre><h4 id="44-">4.4 通用装饰器</h4>
<pre><code># 添加输出日志的功能
def logging(fn):
    def inner(*args, **kwargs):
        print(&quot;--正在努力计算--&quot;)
        result = fn(*args, **kwargs)
        return result

    return inner


# 使用语法糖装饰函数
@logging
def sum_num(*args, **kwargs):
    result = 0
    for value in args:
        result += value

    for value in kwargs.values():
        result += value

    return result


@logging
def subtraction(a, b):
    result = a - b
    print(result)

result = sum_num(1, 2, a=10)
print(result)

subtraction(4, 2)


# 运行结果：
--正在努力计算--
13
--正在努力计算--
2
</code></pre><h4 id="45-">4.5 小结</h4>
<ul>
<li>通用装饰器的语法格式：
<pre><code># 通用装饰器
def logging(fn):
    def inner(*args, **kwargs):
        print('--正在努力计算--')
        result = fn(*args, **kwargs)
        return result 
          
    return inner    
</code></pre></li>
</ul>
<h2 id="5">5.多个装饰器的使用</h2>
<h4 id="51-">5.1 多个装饰器的使用示例代码</h4>
<pre><code>def make_div(func):
    &quot;&quot;&quot;对被装饰的函数的返回值 div标签&quot;&quot;&quot;
    def inner():
        return &quot;&lt;div&gt;&quot; + func() + &quot;&lt;/div&gt;&quot;
    return inner


def make_p(func):
    &quot;&quot;&quot;对被装饰的函数的返回值 p标签&quot;&quot;&quot;
    def inner():
        return &quot;&lt;p&gt;&quot; + func() + &quot;&lt;/p&gt;&quot;
    return inner


# 装饰过程: 1 content = make_p(content) 2 content = make_div(content)
# content = make_div(make_p(content))

@make_div
@make_p
def content():
    return &quot;人生苦短&quot;

result = content()

print(result)

</code></pre><ul>
<li>多个装饰器的装饰过程是：离函数最近的装饰器先装饰，然后后面的装饰器再进行装饰，由内到外的装饰过程</li>
</ul>
<h4 id="52-">5.2 小结</h4>
<ul>
<li>多个装饰器可以对函数进行多个功能的装饰，装饰顺序是由内到外的进行装饰</li>
</ul>
<h2 id="6">6.带有参数的装饰器</h2>
<h4 id="61-">6.1 带有参数的装饰器介绍</h4>
<ul>
<li>带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数，语法格式：<code>@装饰器(参数, ..)</code></li>
</ul>
<h4 id="62-">6.2 示例代码</h4>
<ul>
<li>装饰器只能接收一个参数，并且还是函数类型</li>
<li>在装饰器外面再包裹上一个函数，让最外面的函数接收参数，返回的是装饰器，因为@符号后面必须是装饰器实例。</li>
</ul>
<pre><code># 添加输出日志的功能
def logging(flag):

    def decorator(fn):
        def inner(num1, num2):
            if flag == &quot;+&quot;:
                print(&quot;--正在努力加法计算--&quot;)
            elif flag == &quot;-&quot;:
                print(&quot;--正在努力减法计算--&quot;)
            result = fn(num1, num2)
            return result
        return inner

    # 返回装饰器
    return decorator


# 使用装饰器装饰函数
@logging(&quot;+&quot;)
def add(a, b):
    result = a + b
    return result


@logging(&quot;-&quot;)
def sub(a, b):
    result = a - b
    return result

result = add(1, 2)
print(result)

result = sub(1, 2)
print(result)

</code></pre><h4 id="63-">6.3 小结</h4>
<ul>
<li>使用带有参数的装饰器，其实是在装饰器外面又包裹了一个函数，使用该函数接收参数，返回是装饰器，因为@符号需要配合装饰器实例使用</li>
</ul>
<h2 id="7">7.类装饰器的使用</h2>
<h4 id="71-">7.1 类装饰器的介绍</h4>
<ul>
<li>装饰器还有一种特殊的用法就是类装饰器，就是通过定义一个类来装饰函数。</li>
</ul>
<h4 id="72-">7.2 类装饰器示例代码</h4>
<pre><code>class Check(object):
    def __init__(self, fn):
        # 初始化操作在此完成
        self.__fn = fn

    # 实现__call__方法，表示对象是一个可调用对象，可以像调用函数一样进行调用。
    def __call__(self, *args, **kwargs):
        # 添加装饰功能
        print(&quot;请先登陆...&quot;)
        self.__fn()


@Check
def comment():
    print(&quot;发表评论&quot;)


comment()
</code></pre><ul>
<li>@Check等价于comment = Check(comment),所以需要提供一个init方法，并多增加一个fn参数。</li>
<li>要想类的实例对象能够像函数一样调用，需要在类里面使用call方法，把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。</li>
<li>在call方法里进行对fn函数的装饰，可以添加额外的功能</li>
</ul>
<h4 id="73-">7.3 小结</h4>
<ul>
<li>想要让类的实例对象能够像函数一样进行调用，需要在类里面使用call方法，把类的实例变成可调用对象</li>
<li>类装饰器装饰函数功能在call方法里面进行添加</li>
</ul>

    </div>
    <div class="post-footer">
      
    </div>
  </article>

    </main>
  </body>
</html>
